/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.awt.*;
import java.util.*;
import java.awt.geom.*;

/**
 * Graphs the data
 * @author parkA
 */
public class Grapher {

    private final double bufferX = 15;
    private final double bufferY = 15;
    private double increment, xUnit, yUnit, width, height, rangeX, rangeY, xWidth, yHeight, minY, maxY, minX, maxX;
    private Graphics2D g2;
    private ArrayList<Double> indices, data;
    private Image im;
    private boolean xText;		//if x coordinates are to be displayed
    private boolean yText;		//if y coordinates are to be displayed

    public Grapher() {
        this(false, false);
    }

    public Grapher(boolean x, boolean y) {
        xText = x;
        yText = y;
    }

    public void setup() // set up data values and draws X and Y axes
    {
        g2 = (Graphics2D) im.getGraphics();		// get graphics for writing the image
        width = 1.0 * im.getWidth(null);			// width
        height = 1.0 * im.getHeight(null);		// height
        double[] minMax = minMax(data);
        minY = minMax[0];
        maxY = minMax[1];
        minMax = minMax(indices);
        minX = minMax[0];
        maxX = minMax[1];
        rangeX = maxX - minX;					//how wide the x-axis is going to be in units
        rangeY = maxY - minY;					//how wide the y-axis is going to be in units
        xWidth = width - 2 * bufferX;			//how wide the x-axis is going to be in pixels
        yHeight = height - 2 * bufferY;		//how wide the y-axis is going to be in pixels
        xUnit = xWidth / rangeX;				//conversion factor of x-units to pixels
        yUnit = yHeight / rangeY;				//conversion factor of y-units to pixels
        increment = xWidth / data.size();
        /*X axis*/
        if (minY * yUnit <= 5) //makes sure it doesn't hit the x-axis numbers
        {
            g2.draw(new Line2D.Double(bufferX, height - bufferY + minY * yUnit, width - bufferX, height - bufferY + minY * yUnit));
        }
        /*Y axis*/
        if (minX * xUnit <= 5) //makes sure it doesn't hit the y-axis numbers
        {
            g2.draw(new Line2D.Double(bufferX - minX * xUnit, height - bufferY, bufferX - minX * xUnit, bufferY));
        }

        if (xText) {
            for (double count = .05; count < 1; count += .2) {
                int i = (int) (indices.size() * count);
                g2.drawString(neaten(indices.get(i), 5), (int) (bufferX + increment * (i + .5)), (int) height);
            }
        }
        if (yText) {
            g2.drawString(neaten(maxY, 5), 0, 10);
            g2.drawString(neaten(minY, 5), 0, (int) (yHeight - yUnit * minY) + 10);
        }
    }

    private Image drawLineGraph() // main draw function
    {
        setup();
        yUnit = yHeight / rangeY;
        double x1 = bufferX;
        double y1 = height - bufferY - yUnit * data.get(0);
        for (int i = 1; i < data.size(); i++) {
            //double x2 = bufferX + i * increment;							//looks much better
            double x2 = bufferX + (indices.get(i) - minX) * xUnit;	//is probably the right way
            double y2 = height - bufferY - yUnit * data.get(i);
            Line2D.Double temp = new Line2D.Double(x1, y1, x2, y2);
            g2.draw(temp);
            x1 = x2;
            y1 = y2;
        }
        return im;
    }

    private Image drawScatterGraph() // main draw function
    {
        setup();
        yUnit = yHeight / rangeY;
        double x1 = bufferX;
        double y1 = height - bufferY - yUnit * data.get(0);
        for (int i = 1; i < data.size(); i++) {
            //double x2 = bufferX + i * increment;							//looks much better
            double x2 = bufferX + (indices.get(i) - minX) * xUnit;	//is probably the right way
            double y2 = height - bufferY - yUnit * data.get(i);
            Line2D.Double temp = new Line2D.Double(x1, y1, x1, y1);
            g2.draw(temp);
            x1 = x2;
            y1 = y2;
        }
        return im;
    }

    public Image drawLineGraph(ArrayList<Double> dat, Image ik) {
        data = dat;
        im = ik;
        fakeIndices(data.size());
        Collections.sort(data);
        return drawLineGraph();
    }

    public Image drawScatterGraph(ArrayList<Double> dat, Image ik) {
        data = dat;
        im = ik;
        fakeIndices(data.size());
        Collections.sort(data);
        return drawScatterGraph();
    }
    //data is Y-axis		ind is X-axis

    public Image drawLineGraph(ArrayList<Double> ind, ArrayList<Double> dat, Image ik) {
        data = dat;
        im = ik;
        indices = ind;
        return drawLineGraph();
    }

    public Image scatterGraph(ArrayList<Double> ind, ArrayList<Double> dat, Image ik) {
        data = dat;
        im = ik;
        indices = ind;
        return drawScatterGraph();
    }

    private Image drawBarGraph() {
        setup();
        for (int i = 0; i < data.size(); i++) {
            Rectangle2D.Double temp = new Rectangle2D.Double(bufferX + i * increment, height - bufferY - yUnit * data.get(i), increment, yUnit * data.get(i));
            g2.draw(temp);
        }
        return im;
    }

    public Image drawBarGraph(ArrayList<Double> dat, Image ik) {
        data = dat;
        im = ik;

        Collections.sort(data);
        fakeIndices((int) data.get(0).doubleValue(), (int) data.get(data.size() - 1).doubleValue());
        return drawBarGraph();
    }

    public Image drawBarGraph(ArrayList<Double> dat, ArrayList<Double> ind, Image ik) {
        data = dat;
        indices = ind;
        im = ik;
        return drawBarGraph();
    }

    //X is data[0]. Is amount.		Y is data[1]. Is frequency.
    public ArrayList<Double>[] setUpData(ArrayList<Double> in) {
        Collections.sort(in);
        ArrayList<Double>[] data = new ArrayList[2];
        data[0] = new ArrayList<Double>();
        data[1] = new ArrayList<Double>();
        double last = in.get(0);			//data level to be tested by
        double count = 1;
        for (int i = 1; i < in.size(); i++) {
            double current = in.get(i);	//current data level
            if (current == last) //increment count to show number of objects with this data level
            {
                count++;
            } else //or change to next data level
            {
                data[0].add(last);
                data[1].add(count);
                last = current;
                count = 1;
            }
        }
        data[0].add(last);
        data[1].add(count);
        return data;
    }

    public void fakeIndices(int min, int max) //makes indices from min to max
    {
        indices = new ArrayList<Double>();
        for (double i = min; i <= max; i++) {
            indices.add(i);
        }
    }

    public void fakeIndices(int limit) {
        fakeIndices(1, limit);
    }

    public double[] minMax(ArrayList<Double> data) // get min and max element from the data arraylist
    {
        double min = data.get(0);
        double max = data.get(0);
        int minIndex = 0;
        int maxIndex = 0;
        for (int i = 1; i < data.size(); i++) {
            if (data.get(i) < min) {
                min = data.get(i);
                minIndex = i;
            }
            if (data.get(i) > max) {
                max = data.get(i);
                maxIndex = i;
            }
        }
        double[] temp = {min, max, minIndex, maxIndex};
        return temp;
    }

    public double round(double x, int places) //rounds to places number of decimals
    {
        double pow = Math.pow(10, places);
        x *= pow;
        double y = (x * 10) % 10;
        x = (int) x;
        if (y >= 5.0) {
            x++;
        }
        x /= pow;
        return x;
    }

    public String neaten(double x, int num) //rounds so x has num digits
    {
        int numDigits = (int) Math.log10(x);
        double rounded = x;
        if (num - 1 > numDigits) {
            rounded = round(x, num - 1 - numDigits);
        }
        if (rounded != (int) rounded) {
            return rounded + "";
        }
        return (int) rounded + "";
    }
}
